#include "util_autoborder.h"

#include "util/timer.h"				// log
#include "util/log.h"				// log
#include "util/common_macros.h"		// macros
#include "util/mmgr.h"		    

#include <cassert>

// BORDER EXTENTS INDEX
//
//		0  1  2
//		3     4
//		5  6  7
//
static const int AUTOBORDER_EXTENTS_INDEX[8][2] = {
	{ -1, -1 },
	{ 0, -1 },
	{ 1, -1 },
	{ -1, 0 },
	{ 1, 0 },
	{ -1, 1 },
	{ 0, 1 },
	{ 1, 1 },
};
static const int AUTOBORDER_EXTENTS_C = 8;

AutoBorder::AutoBorder() :
	m_pData(NULL),
	m_Width(0),
	m_Height(0)
{
}

AutoBorder::~AutoBorder()
{
	UTIL_SAFE_DELETEA(m_pData);
}

void AutoBorder::Init(unsigned int Width, unsigned int Height)
{
	assert(Width && Height);

	m_Width = Width;
	m_Height = Height;
	
	UTIL_SAFE_DELETEA(m_pData);
	m_pData = new BORDER_DATA[m_Width * m_Height];
	memset(m_pData, 0, sizeof(BORDER_DATA) * m_Width * m_Height);
}

void AutoBorder::RefreshBorders()
{
	for (int y = 0; y < (int)m_Height; y++)
	for (int x = 0; x < (int)m_Width; x++)
		RefreshBorder(x, y);
}

void AutoBorder::RefreshBorder(int x, int y)
{
	assert(m_pData);

	if ((x < 0) || (x >= (int)m_Width) || (y < 0) || (y >= (int)m_Height)) return;

	int idx = x + (m_Width * y);

	if (m_pData[idx].Extents = GetBorderExtents(x, y))
		ParseData(&m_pData[idx]);
}

void AutoBorder::Tick(float dt)
{
	for (int i = 0; i < BORDER_COUNT; i++)
		m_SpriteA[i].Tick(dt);
}

void AutoBorder::Render(const math::Vec2& Offset, COLOR c)
{
	if (!m_pData) return;
	const math::Vec2 TileSize = GetTileSize();
	const math::Vec2 SpriteOffset = GetSpriteOffset();

	int idx;
	BORDER_TYPE type;
	math::Vec2 Pos;

	m_SpriteBin.Reset();

	for (int i = 0; i < BORDER_COUNT; i++)
		m_SpriteA[i].m_Color = c;

	for (int y = 0; y < (int)m_Height; y++)
	for (int x = 0; x < (int)m_Width; x++)
	{
		idx = x + (m_Width * y);
		
		if (m_pData[idx].Extents)
		{
			for (int i = 0; i < MAX_BORDER_LAYERS; i++)
			{
				type = m_pData[idx].TypeA[i];
				if (type == BT_NONE) break;

				MapScreenPos(x, y, Pos);
				if (CenterSprites())
					Pos += SpriteOffset + (0.5f * (TileSize - m_SpriteA[type].m_Size));
				else
					Pos += SpriteOffset;

				if ((Pos.x >= -m_SpriteA[type].m_Size.x) && (Pos.x <= 1.0f) && (Pos.y >= -m_SpriteA[type].m_Size.y) && (Pos.y <= 1.0f))
					m_SpriteBin.AddSprite(&m_SpriteA[type], Pos);
			}
		}
	}

	m_SpriteBin.Render(Offset, SortSprites());
}

void AutoBorder::RefreshImages()
{
	const math::Vec2 TileSize = GetTileSize();

	for (BORDER_TYPE i = (BORDER_TYPE)(BT_NONE+1); i < BORDER_COUNT; i = (BORDER_TYPE)(i+1))
	{
		m_SpriteA[i].Create( m_BaseImage + "_" + MapTypeToString(i) );
		m_SpriteA[i].m_Additive = BlendSprites();
		if (ResizeSprites())
			m_SpriteA[i].m_Size = TileSize;
	}
}

unsigned char AutoBorder::GetBorderExtents(int x, int y)
{
	unsigned char extents = 0;

	if (IsBorderSpace(x, y))
	{
		for (int i = 0; i < AUTOBORDER_EXTENTS_C; i++)
		{
			if (IsOpenSpace( x+AUTOBORDER_EXTENTS_INDEX[i][0], y+AUTOBORDER_EXTENTS_INDEX[i][1] ))
				extents |= (1 << i);
		}
	}

	return extents;
}

std::string	AutoBorder::MapTypeToString(const BORDER_TYPE& type)
{
	switch (type)
	{
	case BT_WALL_L:			return "l";
	case BT_WALL_R:			return "r";
	case BT_WALL_T:			return "t";
	case BT_WALL_B:			return "b";
	case BT_WALL_CORNER_TL:	return "corner_tl";
	case BT_WALL_CORNER_TR:	return "corner_tr";
	case BT_WALL_CORNER_BL:	return "corner_bl";
	case BT_WALL_CORNER_BR:	return "corner_br";
	case BT_WALL_U_L:		return "u_l";
	case BT_WALL_U_R:		return "u_r";
	case BT_WALL_U_T:		return "u_t";
	case BT_WALL_U_B:		return "u_b";
	case BT_WALL_EDGE_TL:	return "edge_tl";
	case BT_WALL_EDGE_TR:	return "edge_tr";
	case BT_WALL_EDGE_BL:	return "edge_bl";
	case BT_WALL_EDGE_BR:	return "edge_br";
	case BT_WALL_ENCLOSED:	return "closed";
	}

	return "none";
}

void AutoBorder::ParseData(BORDER_DATA* pData)
{
	// BORDER EXTENTS INDEX
	//
	//		0  1  2
	//		3     4
	//		5  6  7
	//

	memset(pData->TypeA, 0, sizeof(pData->TypeA));

	if (!pData->Extents) return;

	int ext[AUTOBORDER_EXTENTS_C];
	for (int i = 0; i < AUTOBORDER_EXTENTS_C; i++)
		ext[i] = (pData->Extents & (1 << i)) ? 1 : 0;

	// ********************************************************************************
	// begin with most-blocked areas and work downwards towards more open areas

	// all four enclosed
	if (ext[1] && ext[3] && ext[4] && ext[6])	pData->TypeA[0] = BT_WALL_ENCLOSED;

	// three sides enclosed
	else if (ext[3] && ext[4] && ext[6])		pData->TypeA[0] = BT_WALL_U_T;
	else if (ext[1] && ext[3] && ext[6])		pData->TypeA[0] = BT_WALL_U_R;
	else if (ext[1] && ext[4] && ext[6])		pData->TypeA[0] = BT_WALL_U_L;
	else if (ext[1] && ext[3] && ext[4])		pData->TypeA[0] = BT_WALL_U_B;

	// two sides enclosed, with opposing edge
	else if (ext[1] && ext[3] && ext[7])	{	pData->TypeA[0] = BT_WALL_CORNER_TL;
												pData->TypeA[1] = BT_WALL_EDGE_BR;		}
	else if (ext[1] && ext[4] && ext[5])	{	pData->TypeA[0] = BT_WALL_CORNER_TR;
												pData->TypeA[1] = BT_WALL_EDGE_BL;		}
	else if (ext[0] && ext[4] && ext[6])	{	pData->TypeA[0] = BT_WALL_CORNER_BR;
												pData->TypeA[1] = BT_WALL_EDGE_TL;		}
	else if (ext[2] && ext[3] && ext[6])	{	pData->TypeA[0] = BT_WALL_CORNER_BL;
												pData->TypeA[1] = BT_WALL_EDGE_TR;		}

	// two sides enclosed
	else if (ext[1] && ext[3])					pData->TypeA[0] = BT_WALL_CORNER_TL;
	else if (ext[1] && ext[4])					pData->TypeA[0] = BT_WALL_CORNER_TR;
	else if (ext[4] && ext[6])					pData->TypeA[0] = BT_WALL_CORNER_BR;
	else if (ext[3] && ext[6])					pData->TypeA[0] = BT_WALL_CORNER_BL;
	else if (ext[1] && ext[6])				{	pData->TypeA[0] = BT_WALL_T;
												pData->TypeA[1] = BT_WALL_B;	}
	else if (ext[3] && ext[4])				{	pData->TypeA[0] = BT_WALL_L;
												pData->TypeA[1] = BT_WALL_R;	}

	// single side and two edges
	else if (ext[1] && ext[5] && ext[7])	{	pData->TypeA[0] = BT_WALL_T;
												pData->TypeA[1] = BT_WALL_EDGE_BL;
												pData->TypeA[2] = BT_WALL_EDGE_BR;	}
	else if (ext[2] && ext[3] && ext[7])	{	pData->TypeA[0] = BT_WALL_L;
												pData->TypeA[1] = BT_WALL_EDGE_TR;
												pData->TypeA[2] = BT_WALL_EDGE_BR;	}
	else if (ext[0] && ext[2] && ext[6])	{	pData->TypeA[0] = BT_WALL_B;
												pData->TypeA[1] = BT_WALL_EDGE_TL;
												pData->TypeA[2] = BT_WALL_EDGE_TR;	}
	else if (ext[0] && ext[4] && ext[5])	{	pData->TypeA[0] = BT_WALL_R;
												pData->TypeA[1] = BT_WALL_EDGE_TL;
												pData->TypeA[2] = BT_WALL_EDGE_BL;	}

	// single side and an edge
	else if (ext[1] && ext[7])				{	pData->TypeA[0] = BT_WALL_T;
												pData->TypeA[1] = BT_WALL_EDGE_BR;	}
	else if (ext[1] && ext[5])				{	pData->TypeA[0] = BT_WALL_T;
												pData->TypeA[1] = BT_WALL_EDGE_BL;	}
	else if (ext[2] && ext[3])				{	pData->TypeA[0] = BT_WALL_L;
												pData->TypeA[1] = BT_WALL_EDGE_TR;	}
	else if (ext[3] && ext[7])				{	pData->TypeA[0] = BT_WALL_L;
												pData->TypeA[1] = BT_WALL_EDGE_BR;	}
	else if (ext[0] && ext[6])				{	pData->TypeA[0] = BT_WALL_B;
												pData->TypeA[1] = BT_WALL_EDGE_TL;	}
	else if (ext[2] && ext[6])				{	pData->TypeA[0] = BT_WALL_B;
												pData->TypeA[1] = BT_WALL_EDGE_TR;	}
	else if (ext[0] && ext[4])				{	pData->TypeA[0] = BT_WALL_R;
												pData->TypeA[1] = BT_WALL_EDGE_TL;	}
	else if (ext[4] && ext[5])				{	pData->TypeA[0] = BT_WALL_R;
												pData->TypeA[1] = BT_WALL_EDGE_BL;	}

	// single side enclosure
	else if (ext[1])							pData->TypeA[0] = BT_WALL_T;
	else if (ext[3])							pData->TypeA[0] = BT_WALL_L;
	else if (ext[4])							pData->TypeA[0] = BT_WALL_R;
	else if (ext[6])							pData->TypeA[0] = BT_WALL_B;

	// four edges
	else if (ext[0] && ext[2] && ext[5] && ext[7])	{	pData->TypeA[0] = BT_WALL_EDGE_TL;
														pData->TypeA[1] = BT_WALL_EDGE_TR;
														pData->TypeA[2] = BT_WALL_EDGE_BL;
														pData->TypeA[3] = BT_WALL_EDGE_BR;	}

	// three edges
	else if (ext[0] && ext[2] && ext[7])	{	pData->TypeA[0] = BT_WALL_EDGE_TL;
												pData->TypeA[1] = BT_WALL_EDGE_TR;
												pData->TypeA[2] = BT_WALL_EDGE_BR;	}
	else if (ext[2] && ext[5] && ext[7])	{	pData->TypeA[0] = BT_WALL_EDGE_TR;
												pData->TypeA[1] = BT_WALL_EDGE_BR;
												pData->TypeA[2] = BT_WALL_EDGE_BL;	}
	else if (ext[0] && ext[5] && ext[7])	{	pData->TypeA[0] = BT_WALL_EDGE_TL;
												pData->TypeA[1] = BT_WALL_EDGE_BL;
												pData->TypeA[2] = BT_WALL_EDGE_BR;	}
	else if (ext[0] && ext[2] && ext[5])	{	pData->TypeA[0] = BT_WALL_EDGE_TL;
												pData->TypeA[1] = BT_WALL_EDGE_TR;
												pData->TypeA[2] = BT_WALL_EDGE_BL;	}

	// two edges
	else if (ext[0] && ext[2])				{	pData->TypeA[0] = BT_WALL_EDGE_TL;
												pData->TypeA[1] = BT_WALL_EDGE_TR;	}
	else if (ext[0] && ext[5])				{	pData->TypeA[0] = BT_WALL_EDGE_TL;
												pData->TypeA[1] = BT_WALL_EDGE_BL;	}
	else if (ext[0] && ext[7])				{	pData->TypeA[0] = BT_WALL_EDGE_TL;
												pData->TypeA[1] = BT_WALL_EDGE_BR;	}
	else if (ext[2] && ext[5])				{	pData->TypeA[0] = BT_WALL_EDGE_TR;
												pData->TypeA[1] = BT_WALL_EDGE_BL;	}
	else if (ext[2] && ext[7])				{	pData->TypeA[0] = BT_WALL_EDGE_TR;
												pData->TypeA[1] = BT_WALL_EDGE_BR;	}
	else if (ext[5] && ext[7])				{	pData->TypeA[0] = BT_WALL_EDGE_BL;
												pData->TypeA[1] = BT_WALL_EDGE_BR;	}

	// single edge only
	else if (ext[0])							pData->TypeA[0] = BT_WALL_EDGE_TL;
	else if (ext[2])							pData->TypeA[0] = BT_WALL_EDGE_TR;
	else if (ext[5])							pData->TypeA[0] = BT_WALL_EDGE_BL;
	else if (ext[7])							pData->TypeA[0] = BT_WALL_EDGE_BR;

	//
	//		0  1  2
	//		3     4
	//		5  6  7
	//
	// BORDER EXTENTS INDEX
}
